const mysql = require('mysql');
const util = require('util');

const { BusiError, SysError } = require('../exception/exception');

class MySQLConn {
    constructor(conn) {
        this.conn = conn;
        this.queryWrap = util.promisify(conn.query).bind(conn);
        this.releaseWrap = util.promisify(conn.release).bind(conn);
        this.beginTransactionWrap = util.promisify(conn.beginTransaction).bind(conn);
        this.commitWrap = util.promisify(conn.commit).bind(conn);
        this.rollbackWrap = util.promisify(conn.rollback).bind(conn);
    }

    getConn() {
        return this.conn;
    }

    async query(sql, args) {
        return await this.queryWrap(sql, args);
    }

    async release() {
        this.releaseWrap();
    }

    async beginTransaction() {
        await this.beginTransactionWrap();
    }

    async commit() {
        await this.commitWrap();
    }

    async rollback() {
        await this.rollbackWrap();
    }
}

/**
 * mysql连接池
 */
class MySQLPool {
    constructor() {
        this.__init__ = false;
    }

    /**
     * mysql配置
     * @param {{host: String, port: Number, user: String, password: String, database: String}} mysqlConfig 
     */
    init(mysqlConfig) {
        console.log("mysql config:", mysqlConfig["sql.mysql.host"], mysqlConfig["sql.mysql.port"], mysqlConfig["sql.mysql.user"], mysqlConfig["sql.mysql.password"], mysqlConfig["sql.mysql.database"]);
        this.pool = mysql.createPool({
            connectionLimit: 10,
            host: mysqlConfig["sql.mysql.host"],
            port: mysqlConfig["sql.mysql.port"],
            user: mysqlConfig["sql.mysql.user"],
            password: mysqlConfig["sql.mysql.password"],
            database: mysqlConfig["sql.mysql.database"],
            characterEncoding: 'utf8mb4',
            autoReconnect: true
        });

        this.pool.on('release', function (connection) {
            console.logw('Connection', connection.threadId, 'released');
        });

        this.__init__ = true;
    }

    /**
     * 获取连接
     * @returns {Promise<MySQLConn>}
     */
    async getConn() {
        if (!this.__init__) {
            throw new BusiError(500, "MySQL尚未初始化， 請調用init()方法初始化！");
        }

        return new Promise((resolve, reject) => {
            this.pool.getConnection(function (err, connection) {
                if (err) {
                    if (err.code === 'PROTOCOL_CONNECTION_LOST') {
                        console.logw('Database connection was closed.');
                    }
                    if (err.code === 'ER_CON_COUNT_ERROR') {
                        console.logw('Database has too many connections.');
                    }
                    if (err.code === 'ECONNREFUSED') {
                        console.logw('Database connection was refused.');
                    }
                    reject(err);
                } else {
                    resolve(new MySQLConn(connection));
                }
            });
        });
    }

    /**
     * 释放连接
     * @param {MySQLConn}conn
     * @returns {Promise<void>}
     */
    async release(conn) {
        await conn.release();
        // pool.releaseConnection(conn)
    }

    /**
     * 事务处理
     * @param {Function(MySQLConn)} task
     * @returns {Promise<void>}
     */
    async doWithTx(task) {
        let conn = await this.getConn();
        let res = null;
        try {
            await conn.beginTransaction();

            res = await task(conn);

            await conn.commit();
        } catch (e) {
            console.logw("do with tx failed:", e);
            await conn.rollback();
            throw e;
        } finally {
            await this.release(conn);
        }

        return res;
    }

    /**
     * 普通查询
     * @param {String} sql
     * @param {Array|null}args
     * @returns {Promise<*>}
     */
    async query(sql, args) {
        let conn = await this.getConn();
        try {
            let res = await conn.query(sql, args);
            return res;
        } finally {
            this.release(conn);
        }
    }
}

let pool = new MySQLPool();
module.exports = pool;